package tfts

import (
	"fmt"
	"github.com/expr-lang/expr"
	"math"
	"math/rand"
	"sort"
	"strings"
)

const (
	tick = 1000
)

const (
	postMana = 0.03
	prevMana = 0.01
)

type TPS [2]int

var (
	Early  TPS = [2]int{400, 600}  // 2阶段
	Normal TPS = [2]int{600, 1000} // 4阶段
	Wild   TPS = [2]int{800, 1200} // 5阶段
	tps    TPS = Normal
)

type Fight struct {
	*champion
	skill    *skill // skill和champion具有相同的成员变量attrMeta,因此不能内嵌
	ticks    int    // 当前时间，单位ms
	casting  bool   // 是否施法中
	Wound    bool   // 是否被减疗
	Shred    bool   // 是否被减抗
	locks    []validI
	shields  []*Shield
	buffs    map[string]*buff
	attrs    []attributeI
	handlers []handlerI
	env      map[string]any // 表达式替换所用变量
	prevCall []func(*Fight) // 开局调用
	postCall []func(*Fight) // 结束调用
	ManaLess bool
	hasItems bool
}

func SetTPS(t TPS) {
	tps = t
}

func Tank(name string) *Fight {
	key := strings.ToLower(name)
	bundle, ok := bundles[key]
	if !ok {
		panic(fmt.Sprintf("champion %s not found", name))
	}
	return bundle.Build()
}

func (b *Bundle) Build() *Fight {
	f := &Fight{
		buffs: make(map[string]*buff),
		env:   make(map[string]any),
	}
	b.championMeta.attach(f)
	f.skill = b.skill
	f.Shred = true
	f.Wound = true
	return f
}

func (f *Fight) Survive(names ...string) {
	if !f.hasItems {
		f.Std3Core()
	}
	f.maxHp = int(f.hp())
	f.startHp = f.maxHp
	f.Health = int(float64(f.maxHp) * f.hpAmp())
	name := f.name
	if len(names) > 0 {
		name = names[0]
	}
	if outputLevel&Local == Local {
		openFd(f.name)
	}
	defer closeFdIfNeed()
	atkTick := -1    // 攻击前摇
	castTick := 0    // 施法前摇
	beatenTick := -1 // 挨打回蓝的tick
	damage := tps[0]
	f.ticks = -1
	for _, prev := range f.prevCall {
		prev(f)
	}
	for f.current() < 60 || f.Health > 0 {
		f.ticks++

		if !f.lockMana() && !f.casting {
			beatenTick++
		}

		if f.ticks >= tick && f.ticks%tick == 0 {
			if f.current() > 15.0 {
				damage = tps[1]
			}
			f.publish(DamagedE)
			actual := int(float64(damage)*f.mitigate()) - f.block
			beaten, dead := f.lose(actual)

			f.showStatus()
			if !dead {
				f.pureTaken += actual
				f.totalTaken += damage
			} else {
				f.pureTaken += actual + f.Health
				f.totalTaken += damage * (beaten + f.Health) / actual
				break
			}
			if f.lockMana() {
				traceLog("%4.1f秒:法力锁定中\n", f.current())
			} else {
				prev := prevMana * float64(damage*beaten/actual)
				post := postMana * float64(beaten)
				mana := int(math.Floor((prev + post) * f.manaAmp()))
				f.mana += mana
				traceLog("%4.1f秒:挨打回复%d蓝量, 当前法力值%d\n", f.current(), mana, f.mana)
			}

			f.DmgRecord = append(f.DmgRecord, actual)
			f.publish(TimeGoE)
		}

		if castTick > 0 {
			castTick--
		}

		if !f.casting {
			atkTick++
		}

		if f.as()*f.speed*float64(atkTick)/float64(tick) >= 1.000 {
			f.attacks()
			if rand.Float64() <= f.strike() {
				traceLog("%4.1f秒:触发双重打击\n", f.current())
				f.attacks()
				tmp := tick
				// 双重打击即以7.0攻速A一下
				f.ticks += int(1.0 / 7.0 * float64(tmp))
			}
			atkTick = 0
		}

		if f.skill != nil && f.charge() {
			f.casting = true
			f.SpendMana += f.castMana()
			castTick = f.skill.swing * tick / 10 // 前摇的单位是100ms
			actionLog("%4.1f秒:第%d次施法读条, 施法时间%.1f秒\n", f.current(), f.CastTimes+1, float64(f.skill.swing)/10)
		}

		if f.casting && castTick == 0 {
			f.publish(BeforeCastE)
			f.skill.cast(f)
			f.publish(AfterCastE)
			f.casting = false
		}
	}
	if !deferredRun {
		metricLog("%10s承伤总时长%.1f, 总折后承伤%d, 总承伤%d\n", name, f.current(), f.pureTaken, f.totalTaken)
	} else {
		results = append(results, result0{
			taken:  f.totalTaken,
			output: fmt.Sprintf("%10s承伤总时长%.1f, 总折后承伤%d, 总承伤%d\n", name, f.current(), f.pureTaken, f.totalTaken),
		})
	}

}

func (f *Fight) PrevCall(call func(fight *Fight)) *Fight {
	f.prevCall = append(f.prevCall, call)
	return f
}

func (f *Fight) PostCall(call func(fight *Fight)) *Fight {
	f.postCall = append(f.postCall, call)
	return f
}

func (f *Fight) charge() bool {
	actual := f.castMana()
	if f.mana >= actual {
		f.mana -= actual
		return true
	}
	return false
}

func (f *Fight) castMana() int {
	return int(float64(f.skill.mana)*f.manaShrink() - float64(f.manaDec()))
}

func (f *Fight) attacks() {
	f.AtkTimes++
	if !f.lockMana() {
		mana := f.hitMana() * int(100+f.manaAmp()) / 100
		f.mana += mana
		actionLog("%4.1f秒:第%d次攻击, 回复法力值%d, 当前法力值%d\n", f.current(), f.AtkTimes, mana, f.mana)
	} else {
		actionLog("%4.1f秒:第%d次攻击,法力锁定中\n", f.current(), f.AtkTimes)
	}
	f.publish(AttackE)
	return
}

func (f *Fight) lose(trueDmg int) (beaten int, dead bool) {
	remain := trueDmg
	dec := 0
	for _, shield := range f.shields {
		lock := f.lockMana()
		remain, dec = shield.Taken(remain)
		if !lock {
			beaten += dec
		}
		if remain == 0 {
			return beaten, false
		}
	}

	f.Health -= remain
	traceLog("%4.1f秒:扣除生命值%d, 剩余生命值%d\n", f.current(), remain, f.Health)
	if !f.lockMana() {
		beaten += remain
	}
	return beaten, f.Health <= 0
}

func (f *Fight) showStatus() {
	traceLog("%4.1f秒:Health=%d, Armor=%d, Durability=%.0f, MaxHp=%0.f, HpAmp=%.0f\n", f.current(), f.Health, f.ar(), 100-f.taken()*100, f.hp(), f.hpAmp()*100)
	traceLog("%4.1f秒:HealAmp=%.0f, ShieldAmp=%0.f, RecoverAmp=%.0f\n", f.current(), f.hlAmp()*100, f.shdAmp()*100, f.rcvAmp()*100)
}

func (f *Fight) current() float64 {
	if f.ticks < 0 {
		return 0
	}
	return float64(f.ticks) / float64(tick)
}

func (f *Fight) Add(src *attrMeta) *attrMeta {
	for _, attr := range f.attrs {
		if match, ok := attr.(*modifier); ok {
			match.Add(src)
			return &match.attrMeta
		}
	}
	panic("can't find holder")
}

func (f *Fight) Recover(c *callable) *Shield {
	amp := 0.0
	mitigate := float64(100+f.ar()) / 100 / f.taken()

	if c.Grow != "" {
		f.Grow(c.name, c.Grow)
	}

	if c.Heal != "" {
		foo := f.parseRecover(c.Heal)
		foo = math.Floor(foo)
		// 默认被减疗
		amp = f.hlAmp() * f.rcvAmp()
		if f.Wound {
			amp *= 0.67
		}
		heal := int(foo * amp)
		actionLog("%4.1f秒:[%s]触发, 治疗生命值%.0f\n", f.current(), c.name, foo)
		f.BeatenMana += foo * amp * (mitigate*prevMana + postMana)
		f.Health += heal
		if f.Health > int(float64(f.maxHp)*f.hpAmp()) {
			overflow := f.Health - int(float64(f.maxHp)*f.hpAmp())
			f.Health = int(float64(f.maxHp) * f.hpAmp())
			traceLog("%4.1f秒:治疗量溢出%d\n", f.current(), overflow)
		} else {
			f.Health += heal
		}
		f.publish(HealedE)
	}

	if c.Shield != "" {
		return f.Shield(c.name, c.Shield, c.duration)
	}

	return nil
}

func (f *Fight) Grow(name, growS string) {
	foo := f.parseRecover(growS)
	foo = math.Floor(foo)
	amp := f.hpAmp()
	grow := int(foo * amp)
	actionLog("%4.1f秒:[%s]触发, 生命值增长%.0f\n", f.current(), name, foo)
	f.Health += grow
	f.Add(HP(int(foo)))
	f.maxHp += int(foo)
}

func (f *Fight) Shield(name, shield string, duration int) *Shield {
	foo := f.parseRecover(shield)
	foo = math.Floor(foo)
	amp := f.shdAmp() * f.rcvAmp()
	actionLog("%4.1f秒:[%s]触发, 生成护盾%.0f\n", f.current(), name, foo)
	sh := &Shield{name: name, champ: f, health: int(foo * amp), end: duration*tick + f.ticks}
	f.addShield(sh)
	f.publish(ShieldE)
	return sh
}

func (f *Fight) parseRecover(rcv string) float64 {
	f.updateEnv()
	program, err := expr.Compile(rcv, expr.Env(f.env))
	if err != nil {
		panic(fmt.Sprintf(`recvoer "%s" parse error: %v`, rcv, err))
	}
	output, err := expr.Run(program, f.env)
	if err != nil {
		panic(fmt.Sprintf(`recover "%s" eval error: %v`, rcv, err))
	}
	switch t := output.(type) {
	case int:
		return float64(t)
	case float64:
		return t
	default:
		panic(fmt.Sprintf(`recover "%s" eval error: %v`, rcv, err))
	}
}

func (f *Fight) lockMana() bool {
	for _, foo := range f.locks {
		if foo.valid() {
			return true
		}
	}
	return false
}

func (f *Fight) publish(flag Event) {
	for _, handler := range f.handlers {
		handler.handle(flag)
	}
}

func (f *Fight) updateEnv() {
	f.env["time"] = f.current()
	f.env["atk"] = f.AtkTimes
	f.env["cast"] = f.CastTimes
	f.env["hp"] = f.hp() * f.hpAmp() / 100
	f.env["lose"] = int(f.hp()*f.hpAmp()) - f.Health
	f.env["start"] = f.startHp
	f.env["ap"] = f.ap()
	f.env["min"] = min
}

func (f *Fight) percent() float64 {
	return float64(f.Health) / f.hp() * f.hpAmp()
}

func (f *Fight) addShield(shield *Shield) {
	f.shields = append(f.shields, shield)
	f.handlers = append(f.handlers, shield)
	sort.Slice(f.shields, func(i, j int) bool {
		return f.shields[i].end < f.shields[j].end
	})
}

func (f *Fight) Var(name string, value any) {
	f.env[name] = value
}
